﻿using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Collections.Generic;
using Microsoft.Reporting.WebForms;
using System.Collections;
using System.Linq;
using Microsoft.SharePoint.WebPartPages;
using System.Data;
using Microsoft.SharePoint.Administration;

namespace ExtendedReportViewer.ExtendedReportViewer
{
    [ToolboxItem(false)]
    public partial class ExtendedReportViewer : System.Web.UI.WebControls.WebParts.WebPart, IWebPartRow
    {
        private class KeyValuePairComperor : IEqualityComparer<KeyValuePair<string, string[]>>
        {

            public bool Equals(KeyValuePair<string, string[]> x, KeyValuePair<string, string[]> y)
            {
                if (x.Value == null || y.Value == null)
                    return x.Key == y.Key && x.Value == null && y.Value == null;
                else
                    return x.Key == y.Key && x.Value.SequenceEqual(y.Value);
            }

            public int GetHashCode(KeyValuePair<string, string[]> obj)
            {
                throw new NotImplementedException();
            }
        }
        
        private List<IWebPartParameters> wpProviders = new List<IWebPartParameters>();
        //private SPList listlogger = null;
        //private string listLoggerPath;
        private IWebPartRow rowProvider = null;

        public ExtendedReportViewer()
        {
            this.Title = "ExtendedReportViewer";
        }
        
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e); 
        }

        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected override void  CreateChildControls()
        {
 	        base.CreateChildControls();
            this.InitializeControl();
        }

        void WebPartManager_WebPartsConnected(object sender, WebPartConnectionsEventArgs e)
        {
            WriteLog("WebPartManager_WebPartsConnected", e.ConsumerConnectionPoint.ID);

            if (e.ConsumerConnectionPoint.ID == "IWebPartParameters")
            {
                var conn = this.WebPartManager.Connections.Cast<WebPartConnection>().FirstOrDefault(x => x.ConsumerConnectionPointID == "IWebPartRow");
                if (conn != default(WebPartConnection))
                {
                    this.WebPartManager.DisconnectWebParts(conn);
                    rowProvider = null;
                }
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            UpdateInnerReportViewer();
        }

        public Dictionary<string, string[]> oldReportParameters
        {
            get
            {
                var prm = (Dictionary<string, string[]>)this.ViewState["oldReportParameters"];
                if (prm == null) prm = new Dictionary<string, string[]>();
                return prm;
            }
            set
            {
                this.ViewState["oldReportParameters"] = value;
            }
        }

        [WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebPartStorage(Storage.Shared), Category("Misc")]
        public string ReportURL
        {
            get
            {
                this.EnsureChildControls();
                var url = this.InnerReportViewer.ServerReport.ReportServerUrl;
                return (url == null ? "":url.ToString());
            }
            set
            {
                this.EnsureChildControls();
                this.InnerReportViewer.ServerReport.ReportServerUrl = new Uri(value);
            }
        }

        [WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebPartStorage(Storage.Shared), Category("Misc")]
        public string ReportPath
        {
            get
            {
                this.EnsureChildControls();
                return this.InnerReportViewer.ServerReport.ReportPath;
            }
            set
            {
                this.EnsureChildControls();
                this.InnerReportViewer.ServerReport.ReportPath = value;
            }
        }

        //[WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebPartStorage(Storage.Shared), Category("Misc")]
        /*public string ListLoggerPath { 
            get
            {
                return listLoggerPath;
            }
            set
            {
                if (listlogger == null || !listlogger.RootFolder.Url.Trim('/').Equals(value.Trim('/')))
                {
                    if (listlogger != null) WriteLog("ListLoggerPath", value);
                    try
                    {
                        listlogger = SPContext.Current.Web.GetList(value);
                    }
                    catch(Exception)
                    {
                        listlogger = null;
                    }
                }
                listLoggerPath = value;
            }
        }*/

        [ConnectionConsumer("IWebPartParameters", "IWebPartParameters", AllowsMultipleConnections=true)]
        public void SetConnectionParameters(IWebPartParameters provider)
        {
            if (provider == null)
            {
                wpProviders.Clear();
                WriteLog("RemoveWebPartConnection", "IWebPartParameters - null");
                return;
            }
            this.EnsureChildControls();
            WriteLog("SetConnectionParameters", "IWebPartParameters");

            wpProviders.Add(provider);
            provider.SetConsumerSchema(GetPropDescrColl());
        }

        [ConnectionConsumer("IWebPartRow", "IWebPartRow", AllowsMultipleConnections = false)]
        public void SetConnectionRow(IWebPartRow provider)
        {
            if (provider == null)
                WriteLog("SetConnectionParameters", "IWebPartRow - null");
            else
                WriteLog("SetConnectionParameters", "IWebPartRow");
            rowProvider = provider;
            
        }

        [ConnectionProvider("IWebPartRowProvider", "IWebPartRowProvider", AllowsMultipleConnections = false)]
        public IWebPartRow GetConnectionRow()
        {
            WriteLog("GetConnectionRow", "IWebPartRowProvider");
            return this;
            //return new ExtendedReportViewer();
        }

        private void WriteLog(string sSource, string sMessage)
        {
            /*if (listlogger == null) return;

            try
            {
                var itm = listlogger.AddItem();
                itm["Title"] = this.Title;
                itm["Source"] = sSource;
                itm["Message"] = sMessage;

                var upd = SPContext.Current.Web.AllowUnsafeUpdates;
                SPContext.Current.Web.AllowUnsafeUpdates = true;
                itm.Update();
                listlogger.Update();
                SPContext.Current.Web.AllowUnsafeUpdates = upd;
            }
            catch(Exception)
            {
                //exception...
            }*/

            SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
            
            if (diagSvc != null)
            {
                SPDiagnosticsCategory cat = diagSvc.Areas["SharePoint Foundation"].Categories["Web Parts"];
                var severity = (sSource.Contains("error") ? TraceSeverity.High : TraceSeverity.Medium);

                if(cat != null)
                    diagSvc.WriteTrace(0, cat, severity, "Title: {0}, Source: {1}, Message: {2}", new object[] { this.Title, sSource, sMessage });
            }
        }

        private PropertyDescriptorCollection GetPropDescrColl()
        {
            var schema = new List<PropertyDescriptor>();
            foreach (var prmInfo in this.InnerReportViewer.ServerReport.GetParameters())
            {
                if (prmInfo.PromptUser && !string.IsNullOrEmpty(prmInfo.Prompt))
                {
                    //schema.Add(TypeDescriptor.CreateProperty(this.GetType(), prmInfo.Name, typeof(string)));
                    schema.Add(new ReportPropertyDescriptor(TypeDescriptor.CreateProperty(this.GetType(), prmInfo.Name, typeof(string)), prmInfo.Prompt));
                }
            }

            return new PropertyDescriptorCollection(schema.ToArray());
        }

        private Dictionary<string, string[]> GetWebPartRow()
        {
            var reportParameters = new Dictionary<string, string[]>();
            if (rowProvider.Schema == null || rowProvider.Schema.Count == 0) return reportParameters;

            WriteLog("GetWebPartRow", "");
            rowProvider.GetRowData(delegate(object rowData)
            {
                var empListData = (DataRowView)rowData;
                var empListCols = rowProvider.Schema.Cast<PropertyDescriptor>();

                foreach (var prmInfo in this.InnerReportViewer.ServerReport.GetParameters())
                {
                    if (prmInfo.PromptUser && !string.IsNullOrEmpty(prmInfo.Prompt))
                    {
                        var name = prmInfo.Name;
                        var column = empListCols.FirstOrDefault(x => x.DisplayName == prmInfo.Prompt);
                        if (column != default(PropertyDescriptor))
                        {
                            if (!reportParameters.ContainsKey(name))
                            {
                                string[] arrvalue;
                                var colvalue = empListData[column.Name];
                                string strvalue = (colvalue == DBNull.Value) ? null:colvalue.ToString();
                                if (string.IsNullOrEmpty(strvalue) || !prmInfo.MultiValue)
                                    arrvalue = new string[] { strvalue };
                                else
                                    arrvalue = strvalue.Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries).Select(x2 => x2.Trim()).ToArray();

                                if (prmInfo.Nullable && (string.IsNullOrEmpty(strvalue) || strvalue.Contains("null") || strvalue.Contains("NULL")))
                                {
                                    reportParameters.Add(name, null);
                                    WriteLog("UpdateInnerReportViewer - GetWebPartRow", name + "=[null]");
                                }
                                else
                                {
                                    reportParameters.Add(name, arrvalue);
                                    WriteLog("UpdateInnerReportViewer - GetWebPartRow", name + "=" + strvalue);
                                }
                            }
                        }
                    }
                }
            });

            return reportParameters;
        }

        private Dictionary<string, string[]> GetWebPartParameters()
        {
            var reportParameters = new Dictionary<string, string[]>();
            var prminfocoll = this.InnerReportViewer.ServerReport.GetParameters();
            int cnt = 0;
            foreach (var provider in wpProviders)
            {
                provider.GetParametersData(delegate(IDictionary prmdata)
                {
                    if (prmdata == null) return;
                    foreach (DictionaryEntry entry in prmdata)
                    {
                        var name = (string)entry.Key;
                        if (!string.IsNullOrEmpty(name) && !reportParameters.ContainsKey(name) && prminfocoll.Any(x => x.Name == name))
                        {
                            string[] arrvalue;
                            string strvalue = (string)entry.Value;
                            if (string.IsNullOrEmpty(strvalue) || !prminfocoll[name].MultiValue)
                                arrvalue = new string[] { strvalue };
                            else
                                arrvalue = strvalue.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x2 => x2.Trim()).ToArray();

                            if (prminfocoll[name].Nullable && (string.IsNullOrEmpty(strvalue) || strvalue.Contains("null") || strvalue.Contains("NULL")))
                            {
                                reportParameters.Add(name, null);
                                WriteLog("UpdateInnerReportViewer - GetParametersData", (++cnt) + ". " + name + "=[null]");
                            }
                            else
                            {
                                reportParameters.Add(name, arrvalue);
                                WriteLog("UpdateInnerReportViewer - GetParametersData", (++cnt) + ". " + name + "=" + strvalue);
                            }
                            
                            if(!string.IsNullOrEmpty(prminfocoll[name].ErrorMessage))
                                WriteLog("UpdateInnerReportViewer - ErrorMessage", cnt + ". " + name + "=" + prminfocoll[name].ErrorMessage);
                        }
                    }
                });
            }

            return reportParameters;
        }

        private void UpdateInnerReportViewer()
        {
            var                         reportParameters = new Dictionary<string,string[]>();
            if (rowProvider != null)    reportParameters = GetWebPartRow();
            if (wpProviders.Count > 0)  GetWebPartParameters().ToList().ForEach(x => reportParameters[x.Key] = x.Value);

            //check if parameters have changed
            //WriteLog("UpdateInnerReportViewer - SequenceEqual", oldReportParameters.Count + "==" + reportParameters.Count);
            var cancelRender = false;
            if (oldReportParameters.Count > 0 && reportParameters.Count > 0)
            {
                cancelRender = (oldReportParameters.Count == reportParameters.Count) ?
                    reportParameters.OrderBy(x => x.Key).SequenceEqual(oldReportParameters.OrderBy(x => x.Key), new KeyValuePairComperor()) 
                    : false;
            }
            oldReportParameters = reportParameters;

            //WriteLog("UpdateInnerReportViewer - cancelRender", cancelRender + " && " + this.Page.IsCallback);
            if (cancelRender) return;
            if (reportParameters.Count == 0) return;

            try
            {
                //this.InnerReportViewer.ServerReport.Refresh();
                this.InnerReportViewer.ServerReport.SetParameters(
                    reportParameters.Select(delegate(KeyValuePair<string, string[]> x)
                        {
                            if (x.Value != null)
                            {
                                WriteLog("UpdateInnerReportViewer - SetParameters", x.Key + "=" + x.Value[0]);
                                return new ReportParameter(x.Key, x.Value);
                            }
                            else
                            {
                                WriteLog("UpdateInnerReportViewer - SetParameters", x.Key + "=[null]");
                                var prm = new ReportParameter(x.Key);
                                prm.Values.Add(null);
                                return prm;
                            }
                        }
                    )
               );
            }
            catch(Exception ex)
            {
                WriteLog("SetParameters error", ex.ToString());
            }
        }

        private DataTable GetReportDataTable()
        {
            var reportParams = this.InnerReportViewer.ServerReport.GetParameters();
            var dataTable = new DataTable();
            foreach (var prmInfo in reportParams)
            {
                if (prmInfo.PromptUser && !string.IsNullOrEmpty(prmInfo.Prompt))
                {
                    var dtc = new DataColumn(prmInfo.Name, typeof(string));
                    dtc.AllowDBNull = prmInfo.Nullable;
                    dtc.Caption = prmInfo.Prompt;
                    dataTable.Columns.Add(dtc);
                }
            }

            var dataRow = dataTable.NewRow();
            foreach (var prmInfo in reportParams)
            {
                if (prmInfo.PromptUser && !string.IsNullOrEmpty(prmInfo.Prompt))
                {
                    if (prmInfo.Values.Count == 0)
                        dataRow[prmInfo.Name] = "";
                    else if (prmInfo.Nullable && prmInfo.Values[0] == null)
                        dataRow[prmInfo.Name] = DBNull.Value;
                    else if (prmInfo.MultiValue)
                        dataRow[prmInfo.Name] = prmInfo.Values.Aggregate((x, y) => x + ";#" + y);
                    else
                        dataRow[prmInfo.Name] = prmInfo.Values[0];
                }
            }
            dataTable.Rows.Add(dataRow);

            return dataTable;
        }

        public void GetRowData(RowCallback callback)
        {
            this.EnsureChildControls();
            WriteLog("GetRowData", "");
            //callback.Invoke(GetReportDataTable().Rows);
            callback.Invoke(GetReportDataTable().DefaultView[0]);
        }

        public PropertyDescriptorCollection Schema
        {
            get 
            {
                this.EnsureChildControls();
                WriteLog("GetSchema", "");
                return GetPropDescrColl();                
                //return TypeDescriptor.GetProperties(GetReportDataTable().DefaultView[0]);
            }
        }
    }
}
